# -*- coding: utf-8 -*-
"""
@Time : 2025/6/6
@Author : xu qian
@File : 全国电动汽车分析仪表盘.py
"""

import pandas as pd
from pyecharts import charts
from pyecharts import options as opts
from pyecharts.globals import JsCode
from pyecharts.charts import Timeline, Bar, Line, Sankey
import os

# 确保result目录存在
os.makedirs('result', exist_ok=True)


# 1. 数据读取与预处理
def load_and_preprocess_data():
    """数据加载与预处理"""
    df = pd.read_excel('电动汽车市场数据_10万条(1).xlsx')
    df_cleaned = df.drop_duplicates().reset_index(drop=True)

    # 省/市/自治区处理
    province_names = ['云南', '吉林', '四川', '安徽', '山东', '山西', '广东', '江苏', '江西', '河北', '河南', '浙江', '海南', '湖北', '湖南', '甘肃',
                      '福建', '贵州', '辽宁', '陕西', '黑龙江', '青海']
    city_names = ['北京', '天津', '上海', '重庆']
    df_cleaned.loc[df_cleaned['电动汽车产地'].isin(province_names), '电动汽车产地'] = df_cleaned['电动汽车产地'] + '省'
    df_cleaned.loc[df_cleaned['电动汽车产地'].isin(city_names), '电动汽车产地'] = df_cleaned['电动汽车产地'] + '市'
    df_cleaned.loc[df_cleaned['电动汽车产地'].isin(['广西']), '电动汽车产地'] = df_cleaned['电动汽车产地'] + '壮族自治区'
    df_cleaned.loc[df_cleaned['电动汽车产地'].isin(['宁夏']), '电动汽车产地'] = df_cleaned['电动汽车产地'] + '回族自治区'
    df_cleaned.loc[df_cleaned['电动汽车产地'].isin(['新疆']), '电动汽车产地'] = df_cleaned['电动汽车产地'] + '维吾尔自治区'
    df_cleaned.loc[df_cleaned['电动汽车产地'].isin(['西藏', '内蒙古']), '电动汽车产地'] = df_cleaned['电动汽车产地'] + '自治区'

    # 时间字段处理
    df_cleaned['电动汽车生产日期'] = pd.to_datetime(df_cleaned['电动汽车生产日期'], errors='coerce')
    df_cleaned['电动汽车生产年'] = df_cleaned['电动汽车生产日期'].dt.strftime("%Y")
    df_cleaned['电动汽车生产日期'] = df_cleaned['电动汽车生产日期'].dt.strftime("%Y-%m")

    return df_cleaned


data = load_and_preprocess_data()


# 2. 各图表生成函数
def create_map_chart(data: pd.DataFrame):
    """生成全国销量地图"""
    map_data = data[['电动汽车产地', '电动汽车销量']].groupby('电动汽车产地').sum().round(2).reset_index()

    map_chart = charts.Map()
    map_chart.add('', [list(z) for z in zip(map_data['电动汽车产地'], map_data['电动汽车销量'])],
                  is_map_symbol_show=False)
    map_chart.set_series_opts(label_opts=opts.LabelOpts(is_show=False))
    map_chart.set_global_opts(
        visualmap_opts=opts.VisualMapOpts(
            min_=min(map_data['电动汽车销量']),
            max_=max(map_data['电动汽车销量']),
            range_color=["lightskyblue", "yellow", "orangered"],
            pos_bottom='10%',
            pos_left='10%'
        ),
        title_opts=opts.TitleOpts(title='各省自治区电动汽车销量分布', pos_left='center')
    )
    map_chart.set_dark_mode()
    map_chart.render('result/全国电动汽车销量地图.html')
    return map_chart


def create_sales_profit_bar(data: pd.DataFrame):
    """生成销量利润对比柱状图"""
    bar_data = data[['电动汽车品牌', '电动汽车销量', '电动汽车利润']].groupby('电动汽车品牌').sum().round(2).reset_index()

    bar = Bar()
    bar.add_xaxis(bar_data['电动汽车品牌'].tolist())
    bar.add_yaxis('电动汽车销量', bar_data['电动汽车销量'].tolist(), gap=0, label_opts=opts.LabelOpts(False))
    bar.add_yaxis('电动汽车利润', bar_data['电动汽车销量'].tolist(), gap=0, label_opts=opts.LabelOpts(False))

    bar.set_global_opts(
        xaxis_opts=opts.AxisOpts(type_='category', axislabel_opts=opts.LabelOpts(rotate=30)),
        tooltip_opts=opts.TooltipOpts(trigger="axis", axis_pointer_type="cross"),
        title_opts=opts.TitleOpts(title='各品牌电动汽车销量利润对比', pos_left='center'),
        legend_opts=opts.LegendOpts(pos_left='85%', orient='vertical', align='right')
    )
    bar.set_dark_mode()
    bar.render('result/品牌销量利润对比柱状图.html')
    return bar


def create_price_distribution_bar(data: pd.DataFrame):
    """生成价格区间分布柱状图"""
    bins = [0, 10, 20, 30, 40, 50, 100, 200, 500, float('inf')]
    labels = ['0-10', '10-20', '20-30', '30-40', '40-50', '50-100', '100-200', '200-500', '500+']

    data['原价区间'] = pd.cut(data['电动汽车原价'] / 10000, bins=bins, labels=labels)
    data['售价区间'] = pd.cut(data['电动汽车售价'] / 10000, bins=bins, labels=labels)

    original_counts = data['原价区间'].value_counts().sort_index()
    selling_counts = data['售价区间'].value_counts().sort_index()

    bar = Bar(init_opts=opts.InitOpts(width='1200px', height='600px'))
    bar.add_xaxis(labels)
    bar.add_yaxis('原价分布', original_counts.tolist(), stack='stack1', color='#5470C6')
    bar.add_yaxis('售价分布', selling_counts.tolist(), stack='stack1', color='#91CC75')

    bar.set_global_opts(
        title_opts=opts.TitleOpts(title='电动汽车价格区间分布对比', subtitle='原价 vs 售价（单位：万元）'),
        xaxis_opts=opts.AxisOpts(name='价格区间(万元)'),
        yaxis_opts=opts.AxisOpts(name='车辆数量'),
        tooltip_opts=opts.TooltipOpts(
            trigger="axis",
            formatter=JsCode("""
                function(params){
                    return '价格区间: ' + params[0].name + '万元<br/>' +
                           '原价车辆数: ' + params[0].value + '<br/>' +
                           '售价车辆数: ' + params[1].value;
                }
            """)
        ),
        datazoom_opts=[opts.DataZoomOpts()]
    )
    bar.set_dark_mode()
    bar.render('result/价格区间分布柱状图.html')
    return bar


def create_price_comparison_bar(data: pd.DataFrame):
    """生成品牌价格对比柱状图"""
    price_data = data[['电动汽车品牌', '电动汽车原价', '电动汽车售价']].groupby('电动汽车品牌').mean().round(2).reset_index()

    bar = Bar(init_opts=opts.InitOpts(width='1000px', height='600px'))
    bar.add_xaxis(price_data['电动汽车品牌'].tolist())
    bar.add_yaxis('平均原价(元)', price_data['电动汽车原价'].tolist(), color='#5470C6')
    bar.add_yaxis('平均售价(元)', price_data['电动汽车售价'].tolist(), color='#91CC75')

    bar.set_global_opts(
        title_opts=opts.TitleOpts(title='不同品牌电动汽车价格对比'),
        tooltip_opts=opts.TooltipOpts(
            trigger="axis",
            formatter=JsCode("""
                function(params){
                    return '品牌：' + params[0].name + '<br/>' + 
                           params[0].seriesName + '：' + params[0].value + '元<br/>' +
                           params[1].seriesName + '：' + params[1].value + '元<br/>' +
                           '价差：' + (params[0].value - params[1].value).toFixed(2) + '元';
                }
            """)
        )
    )
    bar.set_dark_mode()
    bar.render('result/品牌价格对比柱状图.html')
    return bar


def create_monthly_top_products(data: pd.DataFrame):
    """生成月度热销产品动态柱状图"""
    timeline = Timeline()
    timeline.add_schema(symbol='pin', is_auto_play=True, is_loop_play=True)

    for month, group in data.groupby('电动汽车生产日期'):
        top_products = group.groupby('电动汽车品牌')['电动汽车销量'].sum().nlargest(10).reset_index()

        bar = Bar()
        bar.add_xaxis(top_products['电动汽车品牌'].tolist())
        bar.add_yaxis('销量', top_products['电动汽车销量'].tolist())
        bar.reversal_axis()
        bar.set_dark_mode()

        timeline.add(bar, month)

    timeline.render('result/月度热销产品动态柱状图.html')
    return timeline


def create_profit_pie(data: pd.DataFrame):
    """生成品牌利润占比饼图"""
    pie_data = data[['电动汽车品牌', '电动汽车利润']].groupby('电动汽车品牌').sum().reset_index()

    pie = charts.Pie()
    pie.add('', [list(z) for z in zip(pie_data['电动汽车品牌'], pie_data['电动汽车利润'])])
    pie.set_global_opts(title_opts=opts.TitleOpts(title='品牌利润占比'))
    pie.set_dark_mode()
    pie.render('result/品牌利润占比饼图.html')
    return pie


def create_sales_pie(data: pd.DataFrame):
    """生成品牌销量占比饼图"""
    pie_data = data[['电动汽车品牌', '电动汽车销量']].groupby('电动汽车品牌').sum().reset_index()

    pie = charts.Pie()
    pie.add('', [list(z) for z in zip(pie_data['电动汽车品牌'], pie_data['电动汽车销量'])])
    pie.set_global_opts(title_opts=opts.TitleOpts(title='品牌销量占比'))
    pie.set_dark_mode()
    pie.render('result/品牌销量占比饼图.html')
    return pie


def create_sales_trend(data: pd.DataFrame):
    """生成销量趋势折线图"""
    data['年份'] = pd.to_datetime(data['电动汽车生产日期']).dt.year
    recent_data = data[data['年份'] >= pd.Timestamp.now().year - 10]
    sales_data = recent_data.groupby(['电动汽车品牌', '年份'])['电动汽车销量'].sum().unstack()

    line = Line()
    line.add_xaxis(sales_data.columns.astype(str).tolist())

    for brand in sales_data.index:
        line.add_yaxis(brand, sales_data.loc[brand].tolist())

    line.set_global_opts(
        title_opts=opts.TitleOpts(title='近10年销量趋势'),
        datazoom_opts=[opts.DataZoomOpts()]
    )
    line.set_dark_mode()
    line.render('result/销量趋势折线图.html')
    return line


def create_sankey_chart(data: pd.DataFrame):
    """生成品牌车型销售流向桑基图"""
    valid_data = data.dropna(subset=['电动汽车品牌', '车型', '电动汽车销量'])

    nodes = []
    node_names = set()

    for brand in valid_data['电动汽车品牌'].unique():
        nodes.append({"name": brand})
        node_names.add(brand)

    for model in valid_data['车型'].unique():
        nodes.append({"name": model})
        node_names.add(model)

    links = []
    grouped = valid_data.groupby(['电动汽车品牌', '车型'])['电动汽车销量'].sum().reset_index()

    for _, row in grouped.iterrows():
        links.append({
            "source": row['电动汽车品牌'],
            "target": row['车型'],
            "value": float(row['电动汽车销量'])
        })

    sankey = Sankey()
    sankey.add('', nodes, links)
    sankey.set_global_opts(title_opts=opts.TitleOpts(title='品牌车型销售流向'))
    sankey.set_dark_mode()
    sankey.render('result/销售流向桑基图.html')
    return sankey


"""
#FFFFFF 是一种十六进制颜色代码，表示 白色。
# 表示这是一个十六进制颜色代码。
FF 表示红色通道的值（最大值为 255，即全红）。
FF 表示绿色通道的值（最大值为 255，即全绿）。
FF 表示蓝色通道的值（最大值为 255，即全蓝）。
当红色、绿色和蓝色的通道都为最大值时，颜色就是 白色
"""
def create_title(title_str: str):
    # 以饼图创建
    title = charts.Pie(init_opts=opts.InitOpts(theme='dark'))
    title.set_global_opts(title_opts=opts.TitleOpts(
        title=title_str,
        title_textstyle_opts=opts.TextStyleOpts(font_size=36, color='#FFFFFF'), pos_left='center', pos_top='middle'))

    return title


def create_key_index(title: str, sub_title: str):
    """
    创建文字图片
    :param title:标题内容
    :param sub_title:标题下的数值
    :return: 返回生成的饼图对象（文字内容）
    """
    key_chart = charts.Pie(init_opts=opts.InitOpts(theme='dark', width='300px', height='300px'))
    key_chart.set_global_opts(
        title_opts=opts.TitleOpts(
            title=title,
            subtitle=str(sub_title),
            title_textstyle_opts=opts.TextStyleOpts(font_size=24, color='#FFFFFF',
                                                    font_style='oblique',  # font_style='oblique' 斜体
                                                    font_weight='bolder'),  # font_weight='bolder'粗字体
            pos_left='center',
            pos_top='middle'
        )
    )
    return key_chart



# 3. 生成所有图表
print("开始生成可视化图表...")

charts_list = [
    create_title('全国电动汽车销量数据可视化大屏'),
    create_key_index('销量', data['电动汽车销量'].sum()),
    create_key_index('利润', data['电动汽车利润'].sum()),
    create_map_chart(data),
    create_sales_profit_bar(data),
    create_price_distribution_bar(data),
    create_price_comparison_bar(data),
    create_monthly_top_products(data),
    create_profit_pie(data),
    create_sales_pie(data),
    create_sales_trend(data),
    create_sankey_chart(data)
]

print("所有图表已生成到result目录下")

# 4. 创建可视化大屏
print("正在生成可视化大屏...")

page = charts.Page(layout=charts.Page.DraggablePageLayout)
page.add(
    *[chart for chart in charts_list if chart is not None]
)

# page.render('result/result.html')
page.save_resize_html(
    'result/result.html',
    cfg_file='result/chart_config (5).json',
    dest='result/全国电动汽车销售数据可视化大屏.html'
)

print("可视化大屏已生成: result/全国电动汽车销售数据可视化大屏.html")
